using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Runtime.Remoting;
using System.IO;
using Microsoft.CSharp;

namespace CodeDomAssistant
{
    /// <summary>
    /// Execute a CSharp Code Snippet and return results
    /// </summary>
    public class CSharpSnippet
    {
        List<string> assemblies = new List<string>();
        List<string> namespaces = new List<string>();
        CompilerParameters parameters = new CompilerParameters();
        string codesnippet = string.Empty;

        public CSharpSnippet(string codesnippet)
        {
            this.codesnippet = codesnippet;

            parameters.ReferencedAssemblies.Add("System.dll");
            parameters.ReferencedAssemblies.Add("RemoteLoader.dll");

            namespaces.Add("System");
            namespaces.Add("System.Reflection");
            namespaces.Add("RemoteLoader");
        }

        /// <summary>
        /// List of Referenced Assemblies Needed To Run Snippet
        /// </summary>
        public StringCollection ReferencedAssemblies
        {
            get { return parameters.ReferencedAssemblies; }
        }

        /// <summary>
        /// List of Namespaces
        /// </summary>
        public List<string> Namespaces
        {
            get { return namespaces; }
        }

        /// <summary>
        /// Execute Snippet Method
        /// </summary>
        /// <param name="method"></param>
        /// <param name="args"></param>
        /// <returns></returns>
        public object Execute(string method, params object[] args)
        {
            object results = null;

            AppDomain appdomain = null;
            string script_assembly = "script_" + Guid.NewGuid().ToString();

            try
            {
                // Build Assembly Code
                StringBuilder sb = new StringBuilder();
                StringWriter sw = new StringWriter(sb);

                foreach (string ns in this.namespaces)                {
                    sw.WriteLine("using " + ns + ";");
                }

                sw.WriteLine();
                sw.WriteLine("namespace CodeDomAssistant.Script {");
                sw.WriteLine("    // Encapsulate Code Snippet Script");
                sw.WriteLine("    public class CodeSnippet : MarshalByRefObject, IRemoteInterface {");
                sw.WriteLine("        // Invoke Method Through IRemoteInterface");
                sw.WriteLine("        public object Invoke(string method, object[] parms) {");
                sw.WriteLine("            return this.GetType().InvokeMember(method, BindingFlags.InvokeMethod, null, this, parms);");
                sw.WriteLine("        }");
                sw.WriteLine();
                sw.WriteLine(codesnippet);
                sw.WriteLine("    }");
                sw.WriteLine("}");
                sw.Flush();
                sw.Close();

                // compile code snippet into assembly
                parameters.OutputAssembly = script_assembly + ".dll";

                CSharpCodeProvider provider = new CSharpCodeProvider();

                provider.CompileAssemblyFromSource(parameters, sb.ToString());

                // run snippet in its own AppDomain
                AppDomainSetup setup = new AppDomainSetup();
                setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;

                appdomain = AppDomain.CreateDomain("Scripting", null, setup);
  
                ObjectHandle handle = appdomain.CreateInstance("RemoteLoader", "RemoteLoader.RemoteLoaderFactory");

                RemoteLoader.RemoteLoaderFactory factory = (RemoteLoader.RemoteLoaderFactory)handle.Unwrap();

                RemoteLoader.IRemoteInterface objSnippet = factory.Create(script_assembly + ".dll", "CodeDomAssistant.Script.CodeSnippet", null);

                results = objSnippet.Invoke(method, args);
            }
            finally
            {
                if (appdomain != null)
                {
                    AppDomain.Unload(appdomain);
                }

                if (File.Exists(script_assembly + ".dll"))
                {
                    File.Delete(script_assembly + ".dll");
                }
            }

            return results;
        }
    }
}
